package com.newy.service.sys;

import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.newy.core.AppException;
import com.newy.core.BaseService;
import com.newy.core.util.LangUtils;
import com.newy.core.util.Page;
import com.newy.core.web.AppContext;
import com.newy.dao.sys.DepartDao;
import com.newy.dao.sys.RoleUserDao;
import com.newy.dao.sys.UserDao;
import com.newy.domain.sys.Department;
import com.newy.domain.sys.LoginLog;
import com.newy.domain.sys.LoginLog.LogoutType;
import com.newy.domain.sys.Role;
import com.newy.domain.sys.RoleUser;
import com.newy.domain.sys.User;
import com.newy.domain.sys.vo.CurrentLoginInfo;

/**
 * 用户管理服务类
 * @author linzongxue
 *
 */
@Service
@Transactional
public class UserService extends BaseService{
	private static final Logger logger = LoggerFactory.getLogger(UserService.class);
	
	@Resource private UserDao userDao;
	@Resource private RoleUserDao roleUserDao;
	@Resource private DepartDao departDao;
	@Resource private LogService logSvc;
	
	/**
	 * 当前所有登陆用户的信息列表，每个元素的结构为：
	 * userId, loginName, userName, ipAddress, loginTime, browser, sessionId
	 */
	private List<CurrentLoginInfo> currentLoginInfoList = new CopyOnWriteArrayList<CurrentLoginInfo>();
	
	@Transactional(readOnly=true)
	public User getUser(Long id){
		User user = userDao.get(id);
		return user;
	}
	
	/**
	 * 分页查询用户
	 */
	@Transactional(readOnly=true)
	public Page findUsersByPage(Page page, User user){
		return userDao.findPageByExample(page, user, "sortNum", true);
	}
	
	public void saveUser(User user){
		if (!userDao.isPropsUnique(user, "loginName", "isDeleted"))
			throw new AppException("账号名(%s)已存在", user.getLoginName());
		if (user.getIsAdmin())
			throw new AppException("此账号被禁止创建和修改");
		
		boolean isAdd = user.getId() == null;
		if (isAdd){
			user.setCreateUser(AppContext.getLoginUser().getId());
			user.setCreateTime(new Date());
			user.setLoginTimes(0L);
		}
		else{ 
			//修改用户时，保留那些不能被修改的信息，比如密码、最后登陆时间等等
			User oldUser = this.getUser(user.getId());
			if (oldUser == null){
				throw new AppException("保存失败：此用户已被删除");
			}
			user.setPassword(oldUser.getPassword());
			user.setLastLoginTime(oldUser.getLastLoginTime());
			user.setLoginTimes(oldUser.getLoginTimes());
			user.setCreateUser(oldUser.getCreateUser());
			user.setCreateTime(oldUser.getCreateTime());
		}
		//密码加密
		if (LangUtils.isNotEmpty(user.getPassword())){
			user.setPassword(this.encryptPwd(user.getPassword())); //密码加密
		}
		user.setLastModifyUser(AppContext.getLoginUser().getId());
		user.setLastModifyTime(new Date());
		
		userDao.save(user);
	}
	
	/**
	 * 删除指定用户，同时删除其权限及其它资源
	 */
	public void deleteUser(User user){
		//删除用户
		userDao.delete(user);
		//删除与角色的关联
		roleUserDao.clearRolesFromUser(user.getId());
	}

	/**
	 * 删除指定用户，同时删除其权限及其它资源
	 */
	public void deleteUser(Long id){
		deleteUser(getUser(id));
	}

	/**
	 * 删除多个用户，同时删除其权限及其它资源
	 */
	public void deleteUsers(Long[] ids){
		for(Long id : ids){
			deleteUser(id);
		}
	}

	/**
	 * 当前用户修改密码
	 */
	public void changePassword(User user, String oldPwd, String newPwd){
		if (!encryptPwd(oldPwd).equals(user.getPassword())){
			throw new AppException("原密码不正确");
		}
		user.setPassword(encryptPwd(newPwd));
		userDao.save(user);
	}

	@Transactional(readOnly=true)
	public List<Role> getRolesOfUser(Long userId){
		return roleUserDao.getRolesOfUser(userId);
	}

	@Transactional(readOnly=true)
	public List<Role> getUnselectedRolesOfUser(Long userId){
		return roleUserDao.getUnselectedRolesOfUser(userId);
	}
	
	/**
	 * 给指定用户添加指定的角色关联
	 */
	public void addRolesToUser(Long userId, Long[] roleIds){
		for (Long roleId : roleIds){
			RoleUser userRole = new RoleUser();
			userRole.setUserId(userId);
			userRole.setRoleId(roleId);
			roleUserDao.save(userRole);
		}
	}
	
	/**
	 * 从指定用户中移除指定的角色关联
	 */
	public void removeRolesFromUser(Long userId, Long[] roleIds){
		roleUserDao.removeRolesFromUser(userId, roleIds);
	}
	
	/**
	 * 用户登陆
	 * @param loginName
	 * @param password
	 * @return 成功登陆返回LoginInfo对象，失败返回null
	 */
	public User login(String loginName, String password){
		if (loginName == null || password == null){
			return null;
		}
		User user = userDao.findUserByLoginName(loginName);
		//登陆成功，设置登时时间和登陆次数，保存当前登陆信息，并记录登陆日志
		if (user != null && user.getPassword().equals(encryptPwd(password))){
			user.setLastLoginTime(new Date());
			user.setLoginTimes(user.getLoginTimes() + 1);
			//在内存中保存登陆信息
			AppContext.setLoginUser(user);
			//记录登陆日志
			LoginLog loginLog = logSvc.addCurrentLoginLog(user.getId());
			//记录到当前信息列表
			addLoginInfoToCurList(loginLog);
			return user;
		}
		else{
			return null;
		}
	}
	
	/**
	 * 用户退出系统，更新退出时间，并在当前登陆信息列表中删除此记录
	 * @param logout 退出方式是否为用户注销，true表示是用户注销，false表示是会话超时
	 */
	public void logout(){
		String sessionId = AppContext.getRequest().getRequestedSessionId();
		this.deleteCurrentLoginInfo(sessionId, true);
	}

	/**
	 * 删除当前登陆信息信息，同时更新登陆日志。有三种情况会调用此方法：<br/>
	 * ·用户注销
	 * ·会话超时
	 * ·用户被踢除
	 * @param sessionId
	 * @param killSession 是否要杀死会话，不需要杀死时表示是会话超时
	 */
	public void deleteCurrentLoginInfo(String sessionId, boolean killSession){
		for (int i = 0; i < currentLoginInfoList.size(); i ++){
			CurrentLoginInfo curLoginInfo = currentLoginInfoList.get(i);
			if (curLoginInfo.getSessionId().equals(sessionId)){
				//判断退出类型并更新登陆日志
				LogoutType logoutType = null;
				if (!killSession){
					logoutType = LogoutType.TIMEOUT;
				}
				else if (sessionId.equals(AppContext.getRequest().getRequestedSessionId())){
					logoutType = LogoutType.LOGOUT;
				}
				else{
					logoutType = LogoutType.KILLED;
				}
				logSvc.updateLoginTypeAndTime(sessionId, logoutType);
				currentLoginInfoList.remove(i);
				if (killSession){
					//这句必须放在最后，因为invalidate方法会触发Session的sessionDestroyed事件，还会调用一次deleteCurrentLoginInfo方法
					curLoginInfo.getSession().invalidate();
				}
				return;
			}
		}
	}

	/**
	 * 获取当前登陆信息列表，元素为Map，键名为<br/>
	 * userId, loginName, userName, ipAddress, loginTime, browser, sessionId
	 */
	@Transactional(readOnly=true)
	public List<CurrentLoginInfo> getCurrentLoginInfoList(){
		return this.currentLoginInfoList;
	}
	
	/**
	 * 获取指定会话ID对应的当前登陆信息
	 * @param log 登陆日志
	 * @return 找到返回当前登陆信息，找不到返回null
	 */
	public CurrentLoginInfo getCurrentLoginInfo(String sessionId){
		for (CurrentLoginInfo loginInfo : currentLoginInfoList){
			if (loginInfo.getSessionId().equals(sessionId))
				return loginInfo;
		}
		return null;
	}
	
	/**
	 * 杀死会话
	 */
	public void killSession(String sessionId){
		this.deleteCurrentLoginInfo(sessionId, true);
	}

	@Transactional(readOnly=true)
	public List<User> getAllUsersOfDepart(Long orgId){
		return userDao.findAllUsersOfDepart(orgId);
	}
	
	/**
	 * 获取用户所属的机构
	 */
	@Transactional(readOnly=true)
	public Department getDepartOfUser(User user){
		if (user == null) return null;
		return departDao.get(user.getDepartId());
	}
	
	/**
	 * 获取用户所属的机构
	 */
	@Transactional(readOnly=true)
	public Department getDepartOfUser(Long id){
		return this.getDepartOfUser(this.getUser(id));
	}
	
	/**
	 * 添加一条登陆信息到当前登陆信息列表中
	 * @param loginInfo
	 */
	private void addLoginInfoToCurList(LoginLog loginInfo){
		CurrentLoginInfo curLoginInfo = new CurrentLoginInfo();
		User user = AppContext.getLoginUser();//注意,这里不要使用userDao.getUser()
		Department depart = this.getDepartOfUser(user);
		curLoginInfo.setUserId(user.getId());
		curLoginInfo.setLoginName(user.getLoginName());
		curLoginInfo.setUserName(user.getName());
		curLoginInfo.setDepartName(depart != null ? depart.getName() : null);
		curLoginInfo.setLoginTime(loginInfo.getLoginTime());
		curLoginInfo.setIpAddress(loginInfo.getIpAddress());
		curLoginInfo.setBrowser(loginInfo.getBrowser());
		curLoginInfo.setSessionId(loginInfo.getSessionId());
		curLoginInfo.setSession(AppContext.getRequest().getSession());
		currentLoginInfoList.add(curLoginInfo);
	}
	
	/**
	 * 初始化用户数据，如果用户表为空，自动添加登陆名为admin，密码为admin的超级管理员账号
	 */
	public void initUserData(){
		String amdinLoginName = "admin";
		String adminPassword = "admin";
		String adminUserName = "超级管理员";
		User admin = userDao.findUserByLoginName(amdinLoginName);
		if (admin == null){
			logger.info("创建超级管理员账号");
			admin = new User();
			admin.setName(adminUserName);
			admin.setLoginName(amdinLoginName);
			admin.setPassword(encryptPwd(adminPassword));
			userDao.save(admin);
		}
	}

	/**
	 * 密码加密用两次md5
	 */
	private String encryptPwd(String pwd){
		return LangUtils.md5(LangUtils.md5(pwd));
	}
}
